package com.acceptable.qucun.user.service.impl;

import com.acceptable.qucun.file.entity.CurrentDirectory;
import com.acceptable.qucun.file.entity.template.TemplateFile;
import com.acceptable.qucun.file.exception.FileOperationException;
import com.acceptable.qucun.file.mapper.FileDeleteMapper;
import com.acceptable.qucun.file.mapper.FileMimeMapper;
import com.acceptable.qucun.file.mapper.FileShareItemMapper;
import com.acceptable.qucun.file.mapper.TemplateFileMapper;
import com.acceptable.qucun.file.service.TemplateFileService;
import com.acceptable.qucun.file.service.impl.FileDatabaseOperation;
import com.acceptable.qucun.file.service.impl.FilePhysicalOperation;
import com.acceptable.qucun.generic.entity.FileItem;
import com.acceptable.qucun.generic.entity.RedisItem;
import com.acceptable.qucun.generic.entity.UserItem;
import com.acceptable.qucun.generic.entity.VerifyCodeEmailItem;
import com.acceptable.qucun.generic.exception.GenericExcetion;
import com.acceptable.qucun.generic.util.*;
import com.acceptable.qucun.generic.util.result.error.FileErrorResult;
import com.acceptable.qucun.generic.util.result.error.GenericErrorResult;
import com.acceptable.qucun.generic.util.result.error.UserErrorResult;
import com.acceptable.qucun.generic.util.result.success.UserSuccessResult;
import com.acceptable.qucun.user.entity.User;
import com.acceptable.qucun.user.entity.UserInfo;
import com.acceptable.qucun.user.exception.UserOperationException;
import com.acceptable.qucun.user.mapper.UserMapper;
import com.acceptable.qucun.user.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.tika.exception.TikaException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.xml.sax.SAXException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 巴斯罗斯·K·赫斯洛利
 * @since 2021-07-15
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired private VerifyCodeEmailItem emailItem;
    @Autowired private UserMapper userMapper;
    @Autowired private TemplateFileMapper fileMapper;
    @Autowired private FileItem fileItem;
    @Autowired private TemplateFileService fileService;
    @Autowired private RedisItem redisItem;
    @Autowired private UserItem userItem;
    @Autowired private FileDeleteMapper deleteMapper;
    @Autowired private FileMimeMapper mimeMapper;
    @Autowired private FilePhysicalOperation filePhysicalOperation;
    @Autowired private FileShareItemMapper shareItemMapper;
    @Autowired private StringRedisTemplate redisTemplate;

    /**
     *  向注册时使用的邮箱发送信息
     * @param title 邮件标题
     * @param message 欲发送的完整信息
     * @param destEmail 接收邮箱
     */
    private void sendMessageToEmail(String title, String message, String destEmail){
        HtmlEmail email = new HtmlEmail();
        email.setHostName(emailItem.getHostname());
        email.setCharset(emailItem.getCharset());
        try {
            email.addTo(destEmail);
            email.setFrom(emailItem.getSendEmail(), emailItem.getSender());
            email.setAuthentication(emailItem.getSendEmail(), emailItem.getPassword());
            email.setSubject(title);
            email.setMsg(message);
            email.send();
        } catch (EmailException e) {
            e.printStackTrace();
            throw new UserOperationException(UserErrorResult.VERIFY_CODE_ERROR);
        }
    }

    /**
     *  头像显示 下载 上传的检查
     */
    private Object[] headImgCheck(@NonNull Integer userId)
            throws IOException, TikaException, SAXException {
        String filePath = fileItem.getRootDictPrefix() + "/" + userId + "/head_img.png";
        File file = new File(filePath);
        if (!file.exists()){
            file = new File(fileItem.getDefaultHeadImgPath());
        }
        if (file.isDirectory())
            throw new UserOperationException(UserErrorResult.HEAD_IMG_FORMAT_ERROR);
        List<String> mimeList = new ArrayList<>(mimeMapper.selectMimeByType("image"));
        String mime = FileUtil.getMimeByFile(file);
        if (mimeList.size() == 0 || !mimeList.contains(mime))
            throw new UserOperationException(UserErrorResult.HEAD_IMG_FORMAT_ERROR);
        return new Object[] {file, mime};
    }

    @Override
    public void sendVerifyCode(Map<String, String> dataMap,
                               HttpServletRequest request) {
        String verifyCode = RandomUtil.fullNumber(6);
        String email = dataMap.get("email");
        String usage = dataMap.get("usage");
        String operation = MessageUtil.getOperationMap().get(usage);
        Map<String, String> wordMap = new HashMap<>();
        wordMap.put("operation", operation);
        wordMap.put("email", email);
        wordMap.put("verifyCode", verifyCode);

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);

        wordMap.put("expireTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime()));
        String title = MessageUtil.createTitle("verify", operation);
        String content = MessageUtil.createMessage("verify", wordMap);

        // 向邮箱发送验证码
        sendMessageToEmail(title, content, email);

        // redis的key/value 构造 尽量用其他思想完成 不在此处构造
        {
            String key = dataMap.get("email") + "_" + dataMap.get("usage");
            request.setAttribute("redis_key", key);
            request.setAttribute("minutes", redisItem.getVerifyCodeMinutes());

            Map<String, String> redisValueMap = new HashMap<>();
            redisValueMap.put("usage", dataMap.get("usage"));
            redisValueMap.put("verify_code", verifyCode);
            redisValueMap.put("email", email);

            request.setAttribute("redis_map", redisValueMap);
        }

    }

    // 含建表语句 建表以及建表前语句回滚失败
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void registerUnboundByVerifyCode(Map<String, String> dataMap){
        String email = dataMap.get("email");
        // 邮箱重复性检测
        User user = userMapper.selectByEmail(email);
        if(user != null){
            throw new UserOperationException(UserErrorResult.EMAIL_BOUND_ERROR);
        }

        // 密码重复验证
        String password = dataMap.get("password");
        String repeatpwd = dataMap.get("repeatpwd");
        {
            if(!repeatpwd.equals(password)){
                throw new UserOperationException(UserErrorResult.PASSWORD_NOT_EQUALS_ERROR);
            }
        }

        // 生成用户id
        Integer uid = RandomUtil.userIdUnique(8, userMapper);

        // 构造插入用的数据
        // 用户数据
        user = new User();
        {
            String nickname = dataMap.get("nickname");
            user.setEmail(email);
            user.setNickname(nickname);
            user.setPassword(CodeUtil.base64Encode(password));
            user.setUserId(uid);
            user.setServerRoot(fileItem.getRootDictPrefix() + "/" + user.getUserId());
            user.setTotalVolume(20 * 1024 * 1024 * 1024L);
            user.setVersion(1);
        }

        // 第一文件数据
        TemplateFile file = new TemplateFile();
        {
            LocalDateTime dateTime = TimeUtil.date2LocalDateTime(new Date());
            file.setUserId(user.getUserId());
            file.setCreateTime(dateTime);
            file.setLastModifyTime(dateTime);
            file.setFileSize(0L);
        }

        String rootDictPath;
        // 创建基本目录: [全部文件, chunk_tmp, dustbin]
        {
            rootDictPath = fileItem.getRootDictPrefix() + "/" + user.getUserId();
            File userAllFileDict = new File(rootDictPath + "/" + fileItem.getUndeletedFileDictName());
            // 注意文件权限问题
            boolean success = userAllFileDict.mkdirs();

            File chunkTmpDict = new File(rootDictPath + "/" + fileItem.getChunkTempDictName());
            success = success && chunkTmpDict.mkdirs();
            File dustbinDict = new File(rootDictPath + "/" + fileItem.getDustbinDictName());
            success = success && dustbinDict.mkdirs();
            if(!success)
                throw new UserOperationException(UserErrorResult.REGISTER_ERROR);
        }

        try{
            // 创建仅属于当前用户的文件表
            fileMapper.createTable(user.getUserId());

            // 创建仅属于当前用户的删除表
            deleteMapper.createTable(uid);

            // 创建仅属于当前用户的分享子项表
            shareItemMapper.createTable(uid);

            // 插入根目录
            fileMapper.insertInitInfo(file);
            // 发送注册提示邮件
            {
                String operation = "注册";
                String title = MessageUtil.createTitle("tip", operation);
                Map<String, String> wordMap = new HashMap<>();
                wordMap.put("email", email);
                wordMap.put("operation", operation);

                String content = MessageUtil.createMessage("tip", wordMap);
                sendMessageToEmail(title, content, email);
            }

            // 插入用户
            boolean flag =  user.insert();
            if(!flag){
                throw new UserOperationException(UserErrorResult.REGISTER_ERROR);
            }
        } catch (Exception e){
            e.printStackTrace();
            deleteMapper.dropTable(user.getUserId());
            shareItemMapper.dropTable(user.getUserId());
            fileMapper.dropTable(user.getUserId());
            filePhysicalOperation.deleteFile(Arrays.asList(new File(rootDictPath)));
            throw new UserOperationException(UserErrorResult.REGISTER_ERROR);
        }

    }

    @Override
    public String resetPwdBoundByVerifyCode(Map<String, String> dataMap){
        Map<String, String> redisMap = (Map<String, String>) CodeUtil.string2Object(dataMap.get("redis_map"));

        // 从查询得到的redis是否对应
        String sendEmail = null;
        {
            String redisEmail = redisMap.get("email");
            sendEmail = dataMap.get("email");
            if(StringFormatUtil.isEmpty(redisEmail) ||
                    StringFormatUtil.isEmpty(sendEmail) ||
                    !redisEmail.equals(sendEmail)){
                throw new UserOperationException(UserErrorResult.EMAIL_NOT_EQUALS_ERROR);
            }
        }

        // 拦截器已做查询 切面类已判断
        Map<String, Object> userMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("user"));
        User user = CodeUtil.map2Object(userMap, User.class);

        // 密码重复验证
        String password = null;
        String repeatpwd = null;
        {
            password = dataMap.get("password");
            repeatpwd = dataMap.get("repeatpwd");
            if(!repeatpwd.equals(password)){
                throw new UserOperationException(UserErrorResult.PASSWORD_NOT_EQUALS_ERROR);
            }
        }

        if(CodeUtil.base64Encode(password).equals(user.getPassword()))
            throw new UserOperationException(UserErrorResult.PASSWORD_EQUALS_LAST_ERROR);
        String newPwd = CodeUtil.base64Encode(password);
        userMapper.changePasswordByEmail(user.getEmail(), newPwd);

        if (!StringFormatUtil.isEmpty(user.getDeleteTimeStr())){
            // 取消删除帐号的操作
            Date now = new Date();
            LocalDateTime endTime = TimeUtil.date2LocalDateTime(TimeUtil.string2Date(user.getDeleteTimeStr()));
            endTime = endTime.plusDays(userItem.getMaxRestDay());
            if (user.getDeleted() && now.before(TimeUtil.localDateTime2Date(endTime))){
                userMapper.recoveryAccountByEmail(user.getEmail());
            }
        }
        return UserSuccessResult.RESET_PASSWORD_SUCCESS.getCode();
    }

    @Override
    public UserInfo getUserInfo(Integer userId){
        UserInfo userInfo = new UserInfo();
        User user = userMapper.selectByUserId(userId);
        userInfo.setNickname(user.getNickname());
        userInfo.setTotalVolume(user.getTotalVolume());
        userInfo.setUsedVolume(fileService.selectUsedVolume(userId, fileItem.getUndeletedFileDictId()));
        userInfo.setUserId(userId);

        // 查找头像并赋值
        return userInfo;
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                   readOnly = true,
                   propagation = Propagation.REQUIRED)
    public Map<String, Object> getInitInfo(Integer userId, Long initDictId) {
        /*
            一级缓存: 1 3可互换
                1. 程序对数据增删改
                2. Navicat再修改
                3. 程序再查询
                4. 结果与Navicat修改后的一致
         */
        UserInfo userInfo = this.getUserInfo(userId);

        CurrentDirectory rootDict = fileService.selectExistedSingleDictInfo(userId, initDictId, false);
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", UserSuccessResult.USER_INFO_SUCCESS.getCode());
        dataMap.put("user_info", userInfo);
        dataMap.put("current_dict", rootDict);
        return dataMap;
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
    public Map<String, Object> getInitUserInfo(Integer userId){
        Map<String, Object> resMap = new HashMap<>();
        UserInfo userInfo = this.getUserInfo(userId);
        resMap.put("user_info", userInfo);
        resMap.put("code", UserSuccessResult.USER_INFO_SUCCESS.getCode());
        return resMap;

    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public Map<String, Object> updateNormalInfo(Map<String, String> dataMap) {
        Map<String, Object> userMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("user"));

        User user = CodeUtil.map2Object(userMap, User.class);
        if (user == null)
            throw new UserOperationException(UserErrorResult.EMAIL_BOUND_ERROR);

        String nickname = dataMap.get("nickname");
        user.setNickname(nickname);

        userMapper.updateNormalInfo(user);
        Map<String, Object> resMap = this.getInitUserInfo(user.getUserId());
        resMap.put("code", UserSuccessResult.UPDATE_INFO_SUCCESS.getCode());
        return resMap;
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
            readOnly = true)
    public void headImgShow(HttpServletResponse response, Integer userId) {
        try {
//            System.out.println("user_id " + userId);
            File file;
            String mime;
            if (userId != 0){
                Object[] res = this.headImgCheck(userId);
                file = (File) res[0];
                mime = (String) res[1];
            } else{
                file = new File(fileItem.getDefaultHeadImgPath());
                mime = FileUtil.getMimeByFile(file);
            }

            response.setContentType(mime);
            FileInputStream in = new FileInputStream(file);
            ServletOutputStream out = response.getOutputStream();
            byte[] arr = new byte[1024];
            while (in.read(arr) != -1){
                out.write(arr);
            }

            out.close();
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.DOWNLOAD_ERROR);
        }
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    readOnly = true)
    public ResponseEntity<String> headImgDownload(Integer userId) {
        String fileName = "head_img.png";
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Disposition",
                "attachment; filename=\"" + fileName + "\"");

        try {
            Object[] res = this.headImgCheck(userId);
            File file = (File) res[0];
            String mime = (String) res[1];
            byte[] arr = filePhysicalOperation.getFileBinaryData(file);

            headers.set("Content-Type", mime);
            ResponseEntity<String> entity = new ResponseEntity<>(new String(arr), headers, HttpStatus.OK);
            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.DOWNLOAD_ERROR);
        }
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String headImgUpload(Map<String, String> dataMap,
                                byte[] imgData) {
        Map<String, Object> userMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("user"));
        User user = CodeUtil.map2Object(userMap, User.class);
        String path = fileItem.getRootDictPrefix() + "/" + user.getUserId();
        if (StringFormatUtil.isEmpty(user.getHeadPath())){
            user.setHeadPath("/head_img.png");
            userMapper.updateNormalInfo(user);
        }
        File newImg = new File(path + "/head_img.png");
        newImg.deleteOnExit();
        OutputStream out = null;
        try {
            out = new FileOutputStream(newImg);
            out.write(imgData);
            out.close();

            this.headImgCheck(user.getUserId());
            return UserSuccessResult.UPDATE_IMG_SUCCESS.getCode();
        } catch (Exception e) {
            newImg.deleteOnExit();
            e.printStackTrace();
            throw new UserOperationException(UserErrorResult.UPDATE_IMG_ERROR);
        } finally {
            if (out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    readOnly = true)
    public String changeEmailVerifyByVerifyCode(Map<String, String> dataMap) {
        Map<String, String> redisMap = (Map<String, String>) CodeUtil.string2Object(dataMap.get("redis_map"));
        String email = dataMap.get("email");
        String step = dataMap.get("step");
//        for (Map.Entry<String, String> entry: redisMap.entrySet()){
//            System.out.println(entry.getKey() + " " + entry.getValue());
//        }
        // 前端传来第一步数据
        String redis_key = "";
        if ("0".equals(step)){
            Map<String, Object> userMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("user"));
            User user = CodeUtil.map2Object(userMap, User.class);
            int userId = user.getUserId();
            redisMap.put("user_id", String.valueOf(userId));
            redisMap.put("src_email", email);
            redisMap.remove("verify_code");
            redis_key = email + "_change_email";
        }
        else if ("1".equals(step)){
            String src_email = dataMap.get("src_email");
            String redis_value = redisTemplate.opsForValue().get(src_email + "_change_email");
            redisMap = (Map<String, String>) CodeUtil.string2Object(redis_value);
            if (redisMap.get("src_email").equals(email)){
                throw new UserOperationException(UserErrorResult.CHANGE_EMAIL_EQUALS_ERROR);
            }

            // 前端传来第二步数据 但是redis存的不是第一步的数据 异常
            String access_step = redisMap.get("access_step");
            if (StringFormatUtil.isEmpty(access_step) || !"0".equals(access_step))
                throw new GenericExcetion(GenericErrorResult.DATA_NOT_EXIST_ERROR);

            redisMap.put("dest_email", email);
            redis_key = src_email + "_change_email";
        }
        redisMap.put("access_step", step);

        String value = CodeUtil.object2String(redisMap);
        redisTemplate.opsForValue().set(redis_key, value, Duration.ofMinutes(redisItem.getVerifyCodeMinutes()));
        return UserSuccessResult.CHANGE_EMAIL_VERIFY_SUCCESS.getCode();
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String changeEmailByRedis(Map<String, String> dataMap) {
        Map<String, String> redisMap = (Map<String, String>) CodeUtil.string2Object(dataMap.get("redis_map"));
        String sendSrcEmail = dataMap.get("email");
        String redisSrcEmail = redisMap.get("src_email");

        if (!sendSrcEmail.equals(redisSrcEmail))
            throw new GenericExcetion(GenericErrorResult.DATA_NOT_EXIST_ERROR);

        String sendDestEmail = dataMap.get("dest_email");
        String redisDestEmail = redisMap.get("dest_email");

        if (!sendDestEmail.equals(redisDestEmail))
            throw new GenericExcetion(GenericErrorResult.DATA_NOT_EXIST_ERROR);

        Integer userId = Integer.parseInt(redisMap.get("user_id"));
        User user = userMapper.selectById(userId);
        user.setEmail(sendDestEmail);
        int res = userMapper.changeEmailByUserId(sendDestEmail, userId);
        if (res == 1)
            return UserSuccessResult.CHANGE_EMAIL_SUCCESS.getCode();
        throw new UserOperationException(UserErrorResult.CHANGE_EMAIL_ERROR);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String dropBoundAccountByRedis(Map<String, String> dataMap) {
        String email = dataMap.get("email");
        int flag = userMapper.revokeAccount(email, TimeUtil.date2LocalDateTime(new Date()));
        if (flag == 1){
            return UserSuccessResult.DROP_ACCOUNT_SUCCESS.getCode();
        }
        throw new UserOperationException(UserErrorResult.DROP_ACCOUNT_ERROR);
    }

}
